package com.fuxi;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @ClassName AbstractQueuedSynchronizedDemo
 * @Description TODO
 * @Author admin
 * @Date 2021/6/2 15:54
 * @Version 1.0
 **/
public class AbstractQueuedSynchronizedDemo implements Lock {
    
    private static final Sync sync = new Sync();
    
    @Override
    public void lock() {
        sync.acquire(1);
    }
    
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    
    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }
    
    @Override
    public void unlock() {
        sync.release(1);
    }
    
    @NotNull
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
    
    static class Sync extends AbstractQueuedSynchronizer{
    
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    
        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 0){
                throw new IllegalMonitorStateException();
            }
            
            setState(0);
            setExclusiveOwnerThread(null);
            
            return true;
        }
    
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 0;
        }
        
        Condition newCondition(){
            return new ConditionObject();
        }
        
    }
    
    private static Integer flag = 1;
    
    public static void main(String[] args) {
    
        AbstractQueuedSynchronizedDemo lock = new AbstractQueuedSynchronizedDemo();
    
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2;) {
                    try {
                        lock.lock();
                        while (flag == 1){
                            System.out.println("AAA");
                            flag = 2;
                            i++;
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        lock.unlock();
                    }
                    
                }
            }
        });
    
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2;) {
                    try {
                        lock.lock();
                        while (flag == 2){
                            System.out.println("BBB");
                            flag = 3;
                            i++;
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        lock.unlock();
                    }
                
                }
            }
        });
    
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2;) {
                    try {
                        lock.lock();
                        while (flag == 3){
                            System.out.println("CCC");
                            flag = 1;
                            i++;
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        lock.unlock();
                    }
                
                }
            }
        });
    
        threadA.start();
        threadB.start();
        threadC.start();
        
    }
    
}
